using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Security.Claims;
using System.Text;
using System.Web;
using AspNet.Security.OAuth.Weixin;
using Devonline.CloudService.Aliyun;
using Devonline.CloudService.Tencent.Weixin;
using IdentityModel;
using IdentityServer4;
using IdentityServer4.Events;
using IdentityServer4.Extensions;
using IdentityServer4.Services;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;

namespace Devonline.Identity.Admin.Controllers;

/// <summary>
/// This sample controller implements a typical login/logout/provision workflow for local and external accounts.
/// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production!
/// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval
/// </summary>
[SecurityHeaders]
[Authorize]
public class AccountController(
        AdminSetting appSetting,
        UserManager<User> userManager,
        RoleManager<Role> roleManager,
        SignInManager<User> signInManager,
        IdentityDbContext context,
        AuthorizationService identityService,
        IDataService<User> userService,
        IDataService<Role> roleService,
        IDataService<UserClaim, int> userClaimService,
        ILogger<AccountController> logger,
        IEventService events,
        IClientStore clientStore,
        IDistributedCache cache,
        IAuthenticationSchemeProvider schemeProvider,
        IIdentityServerInteractionService interaction,
        IWebHostEnvironment environment,
        ISmsService<SmsModel> smsService,
        IWeixinService weixinService,
        IOptionsMonitor<WeixinAuthenticationOptions> optionsMonitor,
        AuthenticationService authenticationService,
        PhoneNumberCaptchaService phoneNumberCaptchaService
        ) : Controller
{
    private readonly ILogger<AccountController> _logger = logger;
    private readonly AdminSetting _appSetting = appSetting;
    private readonly UserManager<User> _userManager = userManager;
    private readonly RoleManager<Role> _roleManager = roleManager;
    private readonly SignInManager<User> _signInManager = signInManager;
    private readonly IdentityDbContext _context = context;
    private readonly AuthorizationService _authorizationService = identityService;
    private readonly IDataService<User> _userService = userService;
    private readonly IDataService<Role> _roleService = roleService;
    private readonly IDataService<UserClaim, int> _userClaimService = userClaimService;
    private readonly IEventService _events = events;
    private readonly IClientStore _clientStore = clientStore;
    private readonly IDistributedCache _cache = cache;
    private readonly IAuthenticationSchemeProvider _schemeProvider = schemeProvider;
    private readonly IIdentityServerInteractionService _interaction = interaction;
    private readonly IWebHostEnvironment _environment = environment;
    private readonly ISmsService<SmsModel> _smsService = smsService;
    private readonly IWeixinService _weixinService = weixinService;
    private readonly AuthenticationService _authenticationService = authenticationService;
    private readonly PhoneNumberCaptchaService _phoneNumberCaptchaService = phoneNumberCaptchaService;
    private readonly WeixinAuthenticationOptions _authenticationOptions = optionsMonitor.Get(WeixinAuthenticationDefaults.AuthenticationScheme);

    #region Login
    [HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> LoginAsync(string returnUrl)
    {
        var viewModel = await BuildLoginViewModelAsync(returnUrl);
        if (viewModel.IsExternalLoginOnly)
        {
            // we only have one option for logging in and it's an external provider
            return RedirectToAction(nameof(ExternalController.Challenge), "External", new { scheme = viewModel.ExternalLoginScheme, returnUrl });
        }

        return View(viewModel);
    }
    [AllowAnonymous]
    [HttpPost("Account/Login")]
    public async Task<IActionResult> LoginAsync(UserLoginModel loginModel)
    {
        _logger.LogDebug($"收到用户: {loginModel.UserName} 登录请求!");
        var (result, errorMessage) = await SignInAsync(loginModel);
        var returnUrl = HttpUtility.UrlDecode(loginModel.ReturnUrl) ?? CHAR_SLASH.ToString();
        if (result)
        {
            _logger.LogInformation($"用户: {loginModel.UserName} 登录成功, 即将重定向到: {returnUrl}");
            return Redirect(returnUrl);
        }
        else
        {
            var viewModel = await BuildLoginViewModelAsync(returnUrl);
            viewModel.UserName = loginModel.UserName;
            viewModel.ErrorMessage = errorMessage;
            viewModel.Type = loginModel.Type;
            if (viewModel.IsExternalLoginOnly)
            {
                _logger.LogDebug("user {user} use the external login and will redirect to " + returnUrl, loginModel.UserName);
                return RedirectToAction(nameof(ExternalController.Challenge), "External", new { scheme = viewModel.ExternalLoginScheme, returnUrl });
            }

            _logger.LogInformation($"用户: {loginModel.UserName} 登录失败, 错误提示: {errorMessage}");
            return View(viewModel);
        }
    }
    [AllowAnonymous]
    [HttpPost("api/Account/Login")]
    public async Task<IActionResult> UserLoginAsync([FromBody] UserLoginModel loginModel)
    {
        var (result, errorMessage) = await SignInAsync(loginModel);
        if (result)
        {
            _logger.LogInformation($"用户: {loginModel.UserName} 登录成功!");
            return Ok();
        }
        else
        {
            _logger.LogInformation($"用户: {loginModel.UserName} 登录失败, 错误提示: {errorMessage}");
            return BadRequest(errorMessage);
        }
    }
    /// <summary>
    /// 登陆核心方法
    /// </summary>
    /// <param name="loginModel"></param>
    /// <returns></returns>
    private async Task<(bool, string)> SignInAsync(UserLoginModel loginModel)
    {
        _logger.LogDebug("user {user} request to login system", loginModel.UserName);
        if (string.IsNullOrWhiteSpace(loginModel.UserName) || string.IsNullOrWhiteSpace(loginModel.Password))
        {
            _logger.LogDebug("user {user} request to login system but not provid userName and password!", loginModel.UserName);
            return (false, loginModel.Type switch
            {
                LoginType.Password => "必须输入用户名和密码!",
                LoginType.Captcha => "必须输入验证码!",
                _ => "登录方式无效!"
            });
        }

        if (User.Identity?.IsAuthenticated ?? false)
        {
            _logger.LogDebug("user {user} already login success", loginModel.UserName);
            return (true, string.Empty);
        }

        var errorMessage = AccountOptions.ErrorMessageInvalidCredentials;
        switch (loginModel.Type)
        {
            case LoginType.Password:
                _logger.LogDebug("user {user} request to login system for Password type", loginModel.UserName);
                var result = await _signInManager.PasswordSignInAsync(loginModel.UserName, loginModel.Password, loginModel.RememberLogin, lockoutOnFailure: true);
                if (result.Succeeded)
                {
                    var userContext = await _authorizationService.GetUserContextAsync(loginModel.UserName);
                    if (userContext is not null)
                    {
                        var userModel = await _context.Users.FirstOrDefaultAsync(x => x.UserName == loginModel.UserName);
                        if (userModel is not null)
                        {
                            var claims = await _authenticationService.GetUserClaimsAsync(userModel);
                            await _signInManager.SignInWithClaimsAsync(userModel, false, claims);
                            _logger.LogDebug("user {user} login success for Password type", loginModel.UserName);

                            if (_appSetting.WeixinWebSite is not null)
                            {
                                loginModel.ReturnUrl = _authenticationService.GetRedirectUrl(userModel, loginModel.ReturnUrl);
                            }

                            return (true, string.Empty);
                        }
                    }
                }

                await _events.RaiseAsync(new UserLoginFailureEvent(loginModel.UserName, "invalid credentials"));
                if (result.IsLockedOut)
                {
                    errorMessage = AccountOptions.ErrorMessageLockedOut;
                }

                if (result.IsNotAllowed)
                {
                    errorMessage = AccountOptions.ErrorMessageNotAllowed;
                }

                break;
            case LoginType.Captcha:
                //此时, userName 和 password 分别是手机号和验证码
                _logger.LogDebug("user {user} request to login system for Captcha type", loginModel.UserName);
                var user = await _context.Users.FirstOrDefaultAsync(x => x.PhoneNumberConfirmed && x.PhoneNumber == loginModel.UserName);
                if (user is null)
                {
                    errorMessage = AccountOptions.ErrorMessagePhoneNumberUserNotFound;
                }
                else
                {
                    var captcha = await _phoneNumberCaptchaService.ValidateAsync(loginModel.UserName, loginModel.Password);
                    if (captcha is null)
                    {
                        errorMessage = AccountOptions.ErrorMessageSmsCaptchaExpired;
                    }
                    else if (!string.IsNullOrWhiteSpace(captcha.ErrorMessage))
                    {
                        errorMessage = captcha.ErrorMessage;
                    }
                    else
                    {
                        //验证通过, 登录成功
                        var claims = await _authenticationService.GetUserClaimsAsync(user);
                        await _signInManager.SignInWithClaimsAsync(user, false, claims);
                        var userContext = await _authorizationService.GetUserContextAsync(loginModel.UserName);
                        if (userContext is not null)
                        {
                            _logger.LogDebug("user {user} login success for Captcha type", loginModel.UserName);
                            return (true, string.Empty);
                        }

                        errorMessage = AccountOptions.ErrorMessageSmsCaptchaError;
                    }
                }
                break;
            default:
                break;
        }

        _logger.LogDebug("user {user} login failed, " + errorMessage, loginModel.UserName);
        return (false, errorMessage);
    }
    #endregion

    #region Weixin & bind phoneNumber
    /// <summary>
    /// 微信登录, 此方法提供微信小程序等非微信开放平台类似接入方式的登录
    /// </summary>
    /// <returns></returns>
    [AllowAnonymous]
    [HttpGet("api/Account/WeixinLogin")]
    public async Task<IActionResult> WeixinLoginAsync()
    {
        string? code = Request.GetRequestOption<string>(nameof(code));
        string? state = Request.GetRequestOption<string>(nameof(state));
        if (string.IsNullOrWhiteSpace(code))
        {
            return BadRequest("code 参数不可为空!");
        }

        await _weixinService.AuthorizeAsync(code, state);
        await _authenticationService.LoginAsync();
        await _authorizationService.AuthorizeAsync();
        return Ok();
    }

    [HttpGet("Account/BindPhoneNumber")]
    public IActionResult BindPhoneNumber()
    {
        var redirectUrl = HttpContext.GetContextOption<string>(AppSettings.CLAIM_TYPE_REDIRECT_URL) ?? AppSettings.CHAR_SLASH.ToString();
        return View(new BindPhoneNumberViewModel { ReturnUrl = redirectUrl });
    }
    /// <summary>
    /// 验证并绑定手机号码
    /// </summary>
    /// <param name="code">验证码</param>
    /// <returns></returns>
    [HttpPost("Account/ConfirmPhoneNumber")]
    public async Task<IActionResult> ConfirmPhoneNumberAsync([FromForm] string code, [FromForm] string? returnUrl)
    {
        try
        {
            returnUrl ??= Request.GetRequestOption<string>(AppSettings.CLAIM_TYPE_REDIRECT_URL) ?? Request.GetRequestOption<string>(nameof(returnUrl)) ?? HttpContext.GetContextOption<string>(AppSettings.CLAIM_TYPE_REDIRECT_URL) ?? AppSettings.CHAR_SLASH.ToString();
            var captcha = await _phoneNumberCaptchaService.ValidateAsync(code);
            if (captcha.ErrorMessage is not null)
            {
                var viewModel = new BindPhoneNumberViewModel
                {
                    PhoneNumber = captcha.PhoneNumber,
                    ReturnUrl = returnUrl,
                    ErrorMessage = captcha.ErrorMessage
                };

                return View("BindPhoneNumber", viewModel);
            }

            return Redirect(HttpUtility.UrlDecode(returnUrl));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"验证用户 {_userService.UserName} 短信验证码失败, 错误详情: " + ex.GetMessage());
            await SignOutAsync();
            return RedirectToAction("Login");
        }
    }
    [AllowAnonymous]
    [Display(Name = "发送短信验证码")]
    [HttpPost("api/Account/SendCaptcha")]
    public async Task<IActionResult> SendCaptchaAsync(string phoneNumber)
    {
        //如果用户已登录, 则发送短信验证码为绑定手机号码的作用
        if (User.Identity?.IsAuthenticated ?? false && _userService.UserId is not null)
        {
            await _phoneNumberCaptchaService.SendCaptchaAsync(phoneNumber);
            return Ok(true);
        }

        //用户未登录的情况下发送短信验证为使用短信方式登录的作用, 此时, 要求用户已绑定手机号码
        if (await _userService.AnyAsync(x => x.PhoneNumberConfirmed && x.PhoneNumber == phoneNumber))
        {
            await _phoneNumberCaptchaService.SendCaptchaAsync(phoneNumber);
            return Ok(true);
        }

        return BadRequest("手机号码不存在或尚未注册, 请先注册用户!");
    }
    #endregion

    #region logout
    [HttpGet]
    public async Task<IActionResult> LogoutAsync(string logoutId)
    {
        var userName = HttpContext.GetUserName();
        _logger.LogDebug("user {user} will to logout with logout id: " + logoutId, userName);

        await SignOutAsync();

        ArgumentNullException.ThrowIfNull(_appSetting.UserInteraction);
        var home = _appSetting.UserInteraction.Home;
        logoutId ??= Request.GetRequestOption<string>(nameof(logoutId)) ?? await _interaction.CreateLogoutContextAsync();
        if (!string.IsNullOrEmpty(logoutId))
        {
            var context = await _interaction.GetLogoutContextAsync(logoutId);
            var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId ?? context.ClientIds.FirstOrDefault());
            home = client?.ClientUri ?? home;
            //await HttpContext.SignOutAsync(new AuthenticationProperties { RedirectUri = context.SignOutIFrameUrl ?? _appSetting.UserInteraction.Home });
            //return RedirectToPage("/Account/LoggedOut", new LoggedOutViewModel
            //{
            //    LogoutId = logoutId,
            //    ClientName = context.ClientName,
            //    SignOutIframeUrl = context.SignOutIFrameUrl,
            //    PostLogoutRedirectUri = context.PostLogoutRedirectUri ?? _appSetting.UserInteraction.Home,
            //    AutomaticRedirectAfterSignOut = true
            //});

            _logger.LogDebug("user {user} logout success and redirect to: {clientUri}", userName, home);
        }

        return Redirect(home);
    }
    [HttpGet("api/Account/Logout")]
    public async Task<IActionResult> UserLogoutAsync()
    {
        _logger.LogDebug("user {user} will to logout", HttpContext.GetUserName());
        await SignOutAsync();
        return Ok();
    }
    [HttpGet]
    [AllowAnonymous]
    public IActionResult LoggedOut()
    {
        return View();
    }
    /// <summary>
    /// 退出登录的核心逻辑
    /// </summary>
    /// <returns></returns>
    private async Task SignOutAsync()
    {
        await _cache.RemoveAsync(CACHE_USER + HttpContext.GetUserId());
        await _cache.RemoveAsync(CACHE_USER + HttpContext.GetUserName());
        await _signInManager.SignOutAsync();
    }
    #endregion

    #region ResetPassword
    [HttpGet]
    [AllowAnonymous]
    public IActionResult ResetPassword(string? code = default)
    {
        return code == null ? View("Error") : View();
    }
    [HttpGet]
    [AllowAnonymous]
    public IActionResult ResetPasswordConfirmation()
    {
        return View();
    }
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> ResetPassword(ResetPasswordViewModel model)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }

        if (string.IsNullOrWhiteSpace(model.UserName) || string.IsNullOrWhiteSpace(model.Password))
        {
            return BadRequest("用户名和密码不可为空!");
        }

        var user = await _userManager.FindByNameAsync(model.UserName);
        if (user is null || model.Token is null)
        {
            // Don't reveal that the user does not exist
            return RedirectToAction(nameof(ResetPasswordConfirmation), "Account");
        }

        var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(model.Token));
        var result = await _userManager.ResetPasswordAsync(user, code, model.Password);

        if (result.Succeeded)
        {
            return RedirectToAction(nameof(ResetPasswordConfirmation), "Account");
        }

        return View();
    }
    #endregion

    #region ForgotPassword
    [HttpGet]
    [AllowAnonymous]
    public IActionResult ForgotPassword()
    {
        return View(new ForgotPasswordViewModel());
    }
    [HttpGet]
    [AllowAnonymous]
    public IActionResult ForgotPasswordConfirmation()
    {
        return View();
    }
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
    {
        if (ModelState.IsValid && !string.IsNullOrWhiteSpace(model.UserName))
        {
            User? user;

            try
            {
                user = await _userManager.FindByNameAsync(model.UserName);
            }
            catch (Exception ex)
            {
                _logger.LogError($"Error retrieving user by userName ({model.UserName}) for forgot password functionality: {ex.Message}");
                user = null;
            }

            if (user is null || !await _userManager.IsEmailConfirmedAsync(user))
            {
                // Don't reveal that the user does not exist
                return View("ForgotPasswordConfirmation");
            }

            //var code = await _userManager.GeneratePasswordResetTokenAsync(user);
            //code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            //var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code }, HttpContext.Request.Scheme);

            //发送验证码到手机号码
            //await _emailSender.SendEmailAsync(user.Email, _localizer["ResetPasswordTitle"], _localizer["ResetPasswordBody", HtmlEncoder.Default.Encode(callbackUrl)]);

            return View("ForgotPasswordConfirmation");
        }

        return View(model);
    }
    #endregion

    #region other
    [HttpGet]
    [AllowAnonymous]
    public IActionResult AccessDenied()
    {
        return View();
    }
    /// <summary>
    /// 个人信息
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    public async Task<IActionResult> Profile()
    {
        var clientId = Request.GetRequestOption<string>(nameof(IdentitySetting.ClientId).FirstCharToLower());
        if (clientId is not null)
        {
            var client = await _clientStore.FindEnabledClientByIdAsync(clientId);
            return View(new ProfileViewModel { ClientId = clientId, ClientName = client.ClientName, ClientUri = client.ClientUri });
        }

        return View();
    }

    [Display(Name = "获取用户信息")]
    [HttpGet("api/Account/GetUserInfo")]
    public async Task<IActionResult> GetUserInfoAsync()
    {
        _logger.LogDebug("user {user} get user info forced", HttpContext.GetUserName());
        var userContext = await _authorizationService.GetUserContextAsync(force: true);
        if (userContext is null)
        {
            return NotFound();
        }

        return Ok(userContext.GetUserInfo(HttpContext));
    }

    [Display(Name = "用户是否有资源的权限")]
    [HttpGet("api/Account/UserHasPermission")]
    public async Task<IActionResult> UserHasPermissionAsync(string resource) => Ok(await _authorizationService.UserHasPermissionAsync(resource));

    /// <summary>
    /// 给当前用户自动添加角色, 适用于特殊情况下的设置角色, 只能设置内部角色类型更低的类型
    /// </summary>
    /// <param name="roleName">角色名, 角色不能是内部角色以上类型的角色</param>
    /// <returns></returns>
    [Display(Name = "给当前用户自动添加角色")]
    [HttpPost("api/Account/AutoRole/{roleName}")]
    public async Task<IActionResult> AutoRoleAsync(string roleName)
    {
        _logger.LogDebug("user {user} will be auto assigned to role {role}", _userService.UserName, roleName);
        if (await _authorizationService.AutoRoleAsync(roleName))
        {
            return Ok();
        }

        return BadRequest($"未能成功设置角色 {roleName}");
    }
    #endregion

    [Display(Name = "数据迁移")]
    [AllowAnonymous, HttpGet("api/Account/AutoMigration")]
    public async Task<IActionResult> AutoMigrationAsync()
    {
        await _context.AutoMigrationAsync();
        _logger.LogInformation("数据迁移完成!");
        return Ok();
    }

    [Display(Name = "数据迁移")]
    [AllowAnonymous, HttpGet("api/Account/InitAccount")]
    public async Task<IActionResult> InitAccountAsync()
    {
        #region 创建内置角色
        //系统本身
        if (!await _roleManager.RoleExistsAsync(GROUP_SYSTEM))
        {
            var role = new Role
            {
                Name = GROUP_SYSTEM,
                Alias = "系统",
                Type = AuthorizeType.System,
                Description = "系统本身"
            };

            _roleService.Create(role);
            _roleService.Update(role);
            await _roleManager.CreateAsync(role);
        }

        //超管
        if (!await _roleManager.RoleExistsAsync(GROUP_ADMINISTRATOR))
        {
            var role = new Role
            {
                Name = GROUP_ADMINISTRATOR,
                Alias = "超级管理员",
                Type = AuthorizeType.Administrator,
                Description = "超级管理员"
            };

            _roleService.Create(role);
            _roleService.Update(role);
            await _roleManager.CreateAsync(role);
        }

        //开发者
        if (!await _roleManager.RoleExistsAsync(GROUP_DEVELOPER))
        {
            var role = new Role
            {
                Name = GROUP_DEVELOPER,
                Alias = "开发者",
                Type = AuthorizeType.Developer,
                Description = "系统开发者"
            };

            _roleService.Create(role);
            _roleService.Update(role);
            await _roleManager.CreateAsync(role);
        }

        //匿名用户
        if (!await _roleManager.RoleExistsAsync(GROUP_ANONYMOUS))
        {
            var role = new Role
            {
                Name = GROUP_ANONYMOUS,
                Alias = "匿名用户",
                Type = AuthorizeType.Anonymous,
                Description = "匿名用户"
            };

            _roleService.Create(role);
            _roleService.Update(role);
            await _roleManager.CreateAsync(role);
        }
        #endregion

        #region 创建内置用户, 并给内置用户分配角色
        ArgumentException.ThrowIfNullOrWhiteSpace(_appSetting.DefaultPassword);

        //默认超管
        if (!await _userService.AnyAsync(x => x.UserName == USER_ADMINISTRATOR))
        {
            var user = new User
            {
                Name = "超级管理员",
                UserName = USER_ADMINISTRATOR,
                Alias = "超级管理员",
                Type = AuthorizeType.Administrator,
                Description = "超级管理员"
            };
            _userService.Create(user);
            _userService.Update(user);
            await _userManager.CreateAsync(user);
            await _userManager.AddPasswordAsync(user, _appSetting.DefaultPassword);
            await _userManager.AddToRoleAsync(user, GROUP_ADMINISTRATOR);
        }

        //默认开发者
        if (!await _userService.AnyAsync(x => x.UserName == USER_DEVELOPER))
        {
            var user = new User
            {
                Name = "开发者",
                UserName = USER_DEVELOPER,
                Alias = "开发者",
                Type = AuthorizeType.Developer,
                Description = "系统开发者"
            };
            _userService.Create(user);
            _userService.Update(user);
            await _userManager.CreateAsync(user);
            await _userManager.AddPasswordAsync(user, _appSetting.DefaultPassword);
            await _userManager.AddToRoleAsync(user, GROUP_DEVELOPER);
        }

        //系统本身
        if (!await _userService.AnyAsync(x => x.UserName == USER_SYSTEM))
        {
            var user = new User
            {
                Name = "系统",
                UserName = USER_SYSTEM,
                Alias = "系统",
                Type = AuthorizeType.System,
                Description = "系统本身"
            };
            _userService.Create(user);
            _userService.Update(user);
            await _userManager.CreateAsync(user);
            await _userManager.AddToRoleAsync(user, GROUP_SYSTEM);
        }

        //匿名用户
        if (!await _userService.AnyAsync(x => x.UserName == USER_ANONYMOUS))
        {
            var user = new User
            {
                Name = "匿名用户",
                UserName = USER_ANONYMOUS,
                Alias = "匿名用户",
                Type = AuthorizeType.Anonymous,
                Description = "匿名用户"
            };
            _userService.Create(user);
            _userService.Update(user);
            await _userManager.CreateAsync(user);
            await _userManager.AddToRoleAsync(user, GROUP_ANONYMOUS);
        }
        #endregion

        _logger.LogInformation("初始化账户完成!");
        return Ok();
    }

    /// <summary>
    /// 请求access_token
    /// </summary>
    /// <param name="code"></param>
    /// <param name="state"></param>
    /// <returns></returns>
    //public async Task<IActionResult> GetAccessToken(string code, string state)
    //{
    //    var url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="++"&secret="++"&code=&grant_type=authorization_code";
    //}

#if DEBUG
    [AllowAnonymous, HttpGet("api/Account/ResetPasswords")]
    public async Task<IActionResult> ResetAllPasswordsAsync()
    {
        var users = await _userService.GetQueryable(x => x.Type != AuthorizeType.System && x.Type != AuthorizeType.Anonymous).ToListAsync();
        if (users.IsNotNullOrEmpty() && !string.IsNullOrWhiteSpace(_appSetting.DefaultPassword))
        {
            foreach (var user in users)
            {
                var result = await _userManager.RemovePasswordAsync(user);
                if (result.Succeeded)
                {
                    await _userManager.AddPasswordAsync(user, _appSetting.DefaultPassword);
                }
            }
        }

        return Ok();
    }

    [AllowAnonymous, HttpGet("api/Account/SetUserClaims")]
    public async Task<IActionResult> SetUserClaimsAsync()
    {
        var users = await _userService.GetQueryable().ToListAsync();
        if (users.IsNotNullOrEmpty())
        {
            foreach (var user in users)
            {
                await AddUserClaimAsync(user);
            }
        }

        return Ok();
    }
#endif

    #region priviate methods
    /*****************************************/
    /* helper APIs for the AccountController */
    /*****************************************/
    private async Task<LoginViewModel> BuildLoginViewModelAsync(string returnUrl)
    {
        var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
        if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null)
        {
            var local = context.IdP == IdentityServerConstants.LocalIdentityProvider;

            // this is meant to short circuit the UI and only trigger the one external IdP
            var viewModel = new LoginViewModel
            {
                EnableLocalLogin = local,
                ReturnUrl = returnUrl,
                UserName = context?.LoginHint,
            };

            if (!local && context is not null)
            {
                viewModel.ExternalProviders = [new ExternalProvider { AuthenticationScheme = context.IdP }];
            }

            return viewModel;
        }

        var schemes = await _schemeProvider.GetAllSchemesAsync();
        var providers = schemes
            .Where(x => x.DisplayName != null)
            .Select(x => new ExternalProvider
            {
                DisplayName = x.DisplayName ?? x.Name,
                AuthenticationScheme = x.Name,
                Icon = _appSetting.GetPropertyValue($"{x.Name}.{nameof(AuthOptions.Icon)}")?.ToString()
            }).ToList();

        var allowLocal = true;
        if (context?.Client.ClientId != null)
        {
            var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId);
            if (client != null)
            {
                allowLocal = client.EnableLocalLogin;

                if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Count != 0)
                {
                    providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
                }
            }
        }

        var weixin = providers.FirstOrDefault(x => x.AuthenticationScheme == WeixinAuthenticationDefaults.AuthenticationScheme);
        if (weixin is not null && _appSetting.WeixinWebSite is not null)
        {
            weixin.Icon = _appSetting.WeixinWebSite.Icon;
        }

        return new LoginViewModel
        {
            AllowRememberLogin = AccountOptions.AllowRememberLogin,
            EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin,
            ReturnUrl = returnUrl,
            UserName = context?.LoginHint,
            ExternalProviders = [.. providers],
            State = GetState(returnUrl)
        };
    }
    private async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
    {
        var viewModel = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt };

        if (!(HttpContext.User?.Identity?.IsAuthenticated ?? false))
        {
            // if the user is not authenticated, then just show logged out page
            viewModel.ShowLogoutPrompt = false;
            return viewModel;
        }

        var context = await _interaction.GetLogoutContextAsync(logoutId);
        if (context?.ShowSignoutPrompt == false)
        {
            // it's safe to automatically sign-out
            viewModel.ShowLogoutPrompt = false;
            return viewModel;
        }

        // show the logout prompt. this prevents attacks where the user
        // is automatically signed out by another malicious web page.
        return viewModel;
    }
    private async Task<LoggedOutViewModel> BuildLoggedOutViewModelAsync(string logoutId)
    {
        // get context information (client name, post logout redirect URI and iframe for federated signout)
        var logout = await _interaction.GetLogoutContextAsync(logoutId);
        var viewModel = new LoggedOutViewModel
        {
            AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
            PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
            ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
            SignOutIframeUrl = logout?.SignOutIFrameUrl,
            LogoutId = logoutId
        };

        if (HttpContext.User?.Identity?.IsAuthenticated ?? false)
        {
            var idp = HttpContext.User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
            if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider)
            {
                var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
                if (providerSupportsSignout)
                {
                    if (viewModel.LogoutId == null)
                    {
                        // if there's no current logout context, we need to create one
                        // this captures necessary info from the current logged in user
                        // before we signout and redirect away to the external IdP for signout
                        viewModel.LogoutId = await _interaction.CreateLogoutContextAsync();
                    }

                    viewModel.ExternalAuthenticationScheme = idp;
                }
            }
        }

        return viewModel;
    }
    /// <summary>
    /// 给用户添加必须的 claim
    /// </summary>
    /// <param name="user"></param>
    /// <returns></returns>
    private async Task AddUserClaimAsync(User user)
    {
        var claims = new List<Claim>();
        var userClaims = await _userClaimService.GetQueryable(x => x.UserId == user.Id).ToListAsync();
        if (!userClaims.Any(x => x.ClaimType == CLAIM_TYPE_ID))
        {
            claims.Add(new Claim(CLAIM_TYPE_ID, user.Id));
        }

        if (!userClaims.Any(x => x.ClaimType == CLAIM_TYPE_USER_ID))
        {
            claims.Add(new Claim(CLAIM_TYPE_USER_ID, user.Id));
        }

        if (user.UserName is not null && !userClaims.Any(x => x.ClaimType == CLAIM_TYPE_USER_NAME))
        {
            claims.Add(new Claim(CLAIM_TYPE_USER_NAME, user.UserName));
        }

        if (claims.Count != 0)
        {
            var result = await _userManager.AddClaimsAsync(user, claims);
            if (result.Succeeded)
            {
                _logger.LogInformation($"已添加用户: {user.UserName}");
            }
            else
            {
                _logger.LogWarning($"添加用户: {user.UserName} 时出错: {result.Errors.ToJsonString()}");
            }
        }
    }
    /// <summary>
    /// 过滤权限树, 只保留可显示部分
    /// </summary>
    /// <param name="resource"></param>
    private static void CleanResourceTree(ResourceViewModel resource)
    {
        if (resource.Children is not null && resource.Children.Count != 0)
        {
            foreach (var child in resource.Children)
            {
                if (child.ResourceType == ResourceType.Module || child.ResourceType == ResourceType.Page || child.ResourceType == ResourceType.Element)
                {
                    CleanResourceTree(child);
                }
            }

            resource.Children = resource.Children.Where(x => x.ResourceType == ResourceType.Module || x.ResourceType == ResourceType.Page || x.ResourceType == ResourceType.Element).ToList();
        }
    }
    /// <summary>
    /// 根据微信的 scheme 获取 oauth 请求的 state 参数
    /// </summary>
    /// <param name="returnUrl">处理完成后的返回链接地址</param>
    /// <returns></returns>
    private string? GetState(string returnUrl)
    {
        return _authenticationOptions.StateDataFormat?.Protect(new AuthenticationProperties
        {
            RedirectUri = _appSetting.UserInteraction!.Callback,
            Items =
            {
                { nameof(returnUrl), returnUrl },
                { "scheme", WeixinAuthenticationDefaults.AuthenticationScheme }
            }
        });
    }
    #endregion
}